/*******************************************************************************
 * Copyright (c) 2011 Irineu Martins de Lima Moura (imlm2).
 * This file is part of JBeat.
 * 
 *     JBeat is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU Lesser General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 * 
 *     JBeat is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU Lesser General Public License for more details.
 * 
 *     You should have received a copy of the GNU Lesser General Public License
 *     along with JBeat.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * Contributors:
 *     Irineu Martins de Lima Moura (imlm2) - initial API and implementation
 ******************************************************************************/
package br.ufpe.cin.imlm2.jbeat;

import static br.ufpe.cin.imlm2.util.OptionsParser.*;

import java.io.File;
import java.io.FileNotFoundException;
import java.util.List;

import soot.PackManager;
import soot.Transform;
import soot.options.Options;

import br.ufpe.cin.imlm2.jbeat.soot.common.JBeatOptions;
import br.ufpe.cin.imlm2.jbeat.soot.icpoints.TrapInspector;
import br.ufpe.cin.imlm2.jbeat.soot.report.ExceptionReporter;
import br.ufpe.cin.imlm2.jbeat.soot.report.ReportOptions;
import br.ufpe.cin.imlm2.jbeat.soot.throwableset.generation.GeneratesCalculator;
import br.ufpe.cin.imlm2.jbeat.soot.throwableset.generation.RaisesCalculator;
import br.ufpe.cin.imlm2.jbeat.soot.throwableset.propagation.PropagatesCalculator;
import br.ufpe.cin.imlm2.jbeat.soot.traps.EmptyTrapRemover;
import br.ufpe.cin.imlm2.util.BuildClasspath;
import br.ufpe.cin.imlm2.util.OptionsParser;
import br.ufpe.cin.imlm2.util.ParsingException;
import br.ufpe.cin.imlm2.util.SystemProperties;

public class Main {

	public static void printUsage() {
		System.out
				.println("Usage:\n"
						+ " java -jar jbeat.jar MainClass [jbeat_args | soot_args]* \n"
						+ " Where jbeat_args :\n" + "\t "
						+ PARAM_PREFIX
						+ SHOW_USAGE_PARAM
						+ "   (Show this help)\n"
						+ "\t "
						+ PARAM_PREFIX
						+ FULL_REPORT_PARAM
						+ ":[true | false] (Report exceptions of auto-generated methods?)\n"
						+ "\t "
						+ PARAM_PREFIX
						+ SINGLE_FILE_PARAM
						+ ":[true | false] (Generate report in a single file?)\n"
						+ "\t "
						+ PARAM_PREFIX
						+ RUNTIME_EXCEPTIONS_PARAM
						+ ":[true | false] (Consider exceptions raised by the runtime?)\n"
						+ "\t "
						+ PARAM_PREFIX
						+ CALL_GRAPH_PARAM
						+ ":NOME (Callgraph name to be used. Valid names are 'cha' "
						+ "and 'spark'. Defaults to 'cha' (Class Hierarchy Analysis)\n"
						+ "\t "
						+ PARAM_PREFIX
						+ REPORT_CLASSES_PARAM
						+ ":[classe[,classe]*] (Force the inclusion "
						+ "of the classes in the list in the analysis. By default standard java library classes are not include in the report) \n"
						+ "\t "
						+ PARAM_PREFIX
						+ HANDLE_INSPECTION_PARAM
						+ ":[true | false] (Use the handler inspection "
						+ "algorithm to improve the analysis accuracy?)\n"
						+ "\t "
						+ PARAM_PREFIX
						+ ANALYZE_STANDARD_LIBRARIES_PARAM
						+ ":[true | false] (Analyze standard java"
						+ " libraries and consider exceptions thrown by their methods? )\n"
						+ "\t "
						+ PARAM_PREFIX
						+ REPORT_STANDARD_LIBRARIES_PARAM
						+ "[true | false] (Report standard java"
						+ " libraries? )\n"
						+ "\t "
						+ PARAM_PREFIX
						+ SYNTHETIC_METHODS_PARAM
						+ ":[true | false] (Analyze and consider auto-generated methods ?)\n"
						+ "\t "
						+ PARAM_PREFIX
						+ EXCLUDE_REFLECTION_PARAM
						+ ":[true | false] (Consider exceptions thrown by classes in java.lang.reflect.* ?)\n"
						+ "\t "
						+ PARAM_PREFIX
						+ STATIC_INITIALIZERS_PARAM
						+ ":[true | false] (Analyze and consider static initializers methods ('<clinit>') ?)\n"
						+ "\t "
						+ PARAM_PREFIX
						+ REPORT_PATH_PARAM
						+ ":PATH  (Output directory path. Default = \"/reports\")\n"
						+ "\t "
						+ PARAM_PREFIX
						+ CLASS_PATH_PARAM
						+ ":PATHS  (Classpath of the target program. A list of string containing paths to directories,\n"
						+ "jars and class/source/jimple files to be included in the analysis. This should also include the Java Runtime Libraries that are going to be used by the target program.)\n"
						+ "NOTES:\n- Arguments are CASE SENSITIVE (i.e. true !=  TrUe).\n- Unknown arguments will be passed as arguments to Soot.\n"
						+ "- If the target.java.libraries system property is defined then it should contain a list of classpath "
						+ "entries (directories and jars separated by OS specific path separators) of the Java Runtime Libraries that are going to be used by the target program."
						+ " In this case the Java Runtime Libraries should NOT be included in the "
						+ PARAM_PREFIX
						+ CLASS_PATH_PARAM
						+ " paramater.\n"
						+ "- Lists are COMMA separated.");
	}

	public static void main(String[] args) throws FileNotFoundException,
			ParsingException {

		if (args.length > 0) {
			Options ops = Options.v();
			OptionsParser parser = OptionsParser.getInstance();
			parser.parse(args);
			if (!parser.isShowUsage()) {
				/* General options */
				ops.set_whole_program(true);
				ops.setPhaseOption("cg", "all-reachable:true");
				ops.setPhaseOption("cg", "implicit-entry:false");
				ops.set_main_class(parser.getMainClass());
				ops.set_output_format(Options.output_format_jimple);

				if (parser.isVerbose())
					ops.set_verbose(true);

				if (parser.isTime())
					ops.set_time(true);

				if (SystemProperties.TARGET_JAVA_LIBRARIES == null)
					ops.set_prepend_classpath(true);
				String cp = buildTargetClassPath(parser.getClasspath());
				ops.set_soot_classpath(cp);

				// FIXME: Erase the following line
				ops.set_allow_phantom_refs(true);

				EmptyTrapRemover etr = new EmptyTrapRemover();
				PackManager.v().getPack("wjtp")
						.add(new Transform("wjtp.etr", etr));

				boolean stdLibs = parser.isAnalyzeStandardLibraries();
				boolean clinits = parser.isStaticInitializers();
				boolean autoGenerated = parser.isAutoGenerated();

				if (parser.isHandlerInspection()) {
					TrapInspector ti = new TrapInspector();
					PackManager.v().getPack("wjtp")
							.add(new Transform("wjtp.ti", ti));
				}

				if (parser.isAnalyzeRuntime()) {
					GeneratesCalculator gc = new GeneratesCalculator();
					Transform gcTransform = new Transform("wjtp.gc", gc);
					gcTransform.setDeclaredOptions("enabled "
							+ JBeatOptions.STD_LIBRARIES);
					PackManager.v().getPack("wjtp").add(gcTransform);
					ops.setPhaseOption("wjtp.gc", JBeatOptions.STD_LIBRARIES
							+ ":" + stdLibs);
				}

				RaisesCalculator rc = new RaisesCalculator();
				Transform rcTransform = new Transform("wjtp.rc", rc);
				rcTransform.setDeclaredOptions("enabled "
						+ JBeatOptions.STD_LIBRARIES);
				PackManager.v().getPack("wjtp").add(rcTransform);
				ops.setPhaseOption("wjtp.rc", JBeatOptions.STD_LIBRARIES + ":"
						+ stdLibs);

				PropagatesCalculator pc = new PropagatesCalculator();
				Transform pcTransform = new Transform("wjtp.pc", pc);
				pcTransform.setDeclaredOptions("enabled "
						+ JBeatOptions.STD_LIBRARIES + " "
						+ JBeatOptions.CALLGRAPH + " "
						+ JBeatOptions.STATIC_INITIALIZERS + " "
						+ JBeatOptions.SYNTHETIC_ENTITIES);
				PackManager.v().getPack("wjtp").add(pcTransform);
				ops.setPhaseOption("wjtp.pc", JBeatOptions.STD_LIBRARIES + ":"
						+ stdLibs);
				ops.setPhaseOption("wjtp.pc", JBeatOptions.CALLGRAPH + ":"
						+ parser.getCallGraph());
				ops.setPhaseOption("wjtp.pc", JBeatOptions.STATIC_INITIALIZERS
						+ ":" + clinits);
				ops.setPhaseOption("wjtp.pc", JBeatOptions.SYNTHETIC_ENTITIES
						+ ":" + autoGenerated);

				ExceptionReporter er = new ExceptionReporter();
				Transform erTransform = new Transform("wjtp.er", er);
				erTransform.setDeclaredOptions("enabled "
						+ ReportOptions.STD_LIBRARIES + " "
						+ ReportOptions.FULL_REPORT + " "
						+ ReportOptions.SINGLE_FILE + " "
						+ ReportOptions.REPORT_PATH + " "
						+ ReportOptions.REPORT_CLASSES);
				PackManager.v().getPack("wjtp").add(erTransform);
				ops.setPhaseOption("wjtp.er", ReportOptions.STD_LIBRARIES + ":"
						+ parser.isReportStandardLibraries());
				ops.setPhaseOption("wjtp.er", ReportOptions.FULL_REPORT + ":"
						+ parser.isFullReport());
				ops.setPhaseOption("wjtp.er", ReportOptions.SINGLE_FILE + ":"
						+ parser.isSingleFile());
				ops.setPhaseOption("wjtp.er", ReportOptions.REPORT_PATH + ":"
						+ parser.getReportPath());
				if (parser.getClassReportIncludeList().size() > 0) {
					StringBuffer sb = new StringBuffer();
					for (String s : parser.getClassReportIncludeList()) {
						sb.append(s + ",");
					}
					sb.deleteCharAt(sb.length() - 1);
					ops.setPhaseOption("wjtp.er", ReportOptions.REPORT_CLASSES
							+ ":" + sb);
				}

				soot.Main.main(new String[] { parser.getMainClass() });

				deleteSootOutput();

			} else {
				printUsage();
			}
		} else {
			printUsage();
		}

	}

	private static void deleteSootOutput() {
		File sootOutput = new File("sootOutput");
		if (sootOutput.exists() && sootOutput.isDirectory()) {
			deleteDirectory(sootOutput);
		}

	}

	private static void deleteDirectory(File dir) {
		for (File f : dir.listFiles()) {
			if (f.isDirectory())
				deleteDirectory(f);
			else
				f.delete();
		}
		dir.delete();
	}

	private static String buildTargetClassPath(List<String> cp) {
		List<String> targetCP = cp;
		if (SystemProperties.TARGET_JAVA_LIBRARIES != null) {
			String[] dirs = SystemProperties.TARGET_JAVA_LIBRARIES
					.split(SystemProperties.PATH_SEPARATOR);
			for (String dir : dirs) {
				File directory = new File(dir);
				targetCP.addAll(BuildClasspath.buildFromCPEntries(directory,
						false));

			}
		}
		StringBuffer classpath = new StringBuffer();
		for (String path : targetCP) {
			classpath.append(path + SystemProperties.PATH_SEPARATOR);
		}
		if (classpath.length() > 0) {
			classpath.delete(
					classpath.lastIndexOf(SystemProperties.PATH_SEPARATOR),
					classpath.length());
		}
		return classpath.toString();
	}

}
